{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f832b6a4",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-11-03T08:14:59.998516Z",
     "iopub.status.busy": "2025-11-03T08:14:59.998379Z",
     "iopub.status.idle": "2025-11-03T08:15:10.711309Z",
     "shell.execute_reply": "2025-11-03T08:15:10.711009Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      "  0%|          | 0.00/9.91M [00:00<?, ?B/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      "  0%|          | 32.8k/9.91M [00:00<00:43, 229kB/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      "  1%|          | 65.5k/9.91M [00:00<00:45, 215kB/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      "  2%|▏         | 197k/9.91M [00:00<00:19, 494kB/s] "
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      "  4%|▍         | 393k/9.91M [00:00<00:12, 790kB/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      "  8%|▊         | 786k/9.91M [00:00<00:06, 1.39MB/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      " 16%|█▌        | 1.61M/9.91M [00:00<00:03, 2.66MB/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      " 33%|███▎      | 3.24M/9.91M [00:01<00:01, 5.13MB/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      " 66%|██████▌   | 6.52M/9.91M [00:01<00:00, 9.98MB/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      "100%|██████████| 9.91M/9.91M [00:01<00:00, 7.51MB/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      "  0%|          | 0.00/28.9k [00:00<?, ?B/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      "100%|██████████| 28.9k/28.9k [00:00<00:00, 233kB/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      "100%|██████████| 28.9k/28.9k [00:00<00:00, 231kB/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      "  0%|          | 0.00/1.65M [00:00<?, ?B/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      "  4%|▍         | 65.5k/1.65M [00:00<00:02, 602kB/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      "  8%|▊         | 131k/1.65M [00:00<00:02, 520kB/s] "
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      " 12%|█▏        | 197k/1.65M [00:00<00:03, 484kB/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      " 24%|██▍       | 393k/1.65M [00:00<00:01, 802kB/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      " 48%|████▊     | 786k/1.65M [00:00<00:00, 1.43MB/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      " 97%|█████████▋| 1.61M/1.65M [00:00<00:00, 2.69MB/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      "100%|██████████| 1.65M/1.65M [00:00<00:00, 1.86MB/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      "  0%|          | 0.00/4.54k [00:00<?, ?B/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r\n",
      "100%|██████████| 4.54k/4.54k [00:00<00:00, 6.64MB/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import torch\n",
    "from torchvision import datasets, transforms\n",
    "\n",
    "\n",
    "np.random.seed(0)\n",
    "torch.manual_seed(0)\n",
    "\n",
    "# 准备数据转换：把图片变成张量\n",
    "transform = transforms.ToTensor()\n",
    "\n",
    "# 下载训练集和测试集\n",
    "train_dataset = datasets.MNIST(root=\"data\", train=True, download=True, transform=transform)\n",
    "test_dataset = datasets.MNIST(root=\"data\", train=False, download=True, transform=transform)\n",
    "\n",
    "# 把图片转成二维数组，每一行是一张图片的像素\n",
    "train_images = train_dataset.data.numpy().reshape(-1, 28 * 28).astype(np.float32) / 255.0\n",
    "test_images = test_dataset.data.numpy().reshape(-1, 28 * 28).astype(np.float32) / 255.0\n",
    "\n",
    "# 标签直接转成numpy数组，方便后面处理\n",
    "train_labels = train_dataset.targets.numpy()\n",
    "test_labels = test_dataset.targets.numpy()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "f94bdf39",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-11-03T08:15:10.712622Z",
     "iopub.status.busy": "2025-11-03T08:15:10.712394Z",
     "iopub.status.idle": "2025-11-03T08:15:11.331603Z",
     "shell.execute_reply": "2025-11-03T08:15:11.331374Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "第 1 轮训练结束，训练集准确率: 0.8611166666666666 错误样本数: 9154\n",
      "第 2 轮训练结束，训练集准确率: 0.8835666666666666 错误样本数: 7604\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "第 3 轮训练结束，训练集准确率: 0.8573333333333333 错误样本数: 7354\n",
      "第 4 轮训练结束，训练集准确率: 0.9011666666666667 错误样本数: 7226\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "第 5 轮训练结束，训练集准确率: 0.8956833333333334 错误样本数: 6980\n"
     ]
    }
   ],
   "source": [
    "# 初始化感知机参数\n",
    "num_classes = 10\n",
    "input_dim = 28 * 28\n",
    "weights = np.zeros((num_classes, input_dim), dtype=np.float32)\n",
    "bias = np.zeros(num_classes, dtype=np.float32)\n",
    "\n",
    "# 训练超参数\n",
    "epochs = 5\n",
    "\n",
    "# 逐个样本训练，按课上讲的做多分类\n",
    "for epoch in range(epochs):\n",
    "    # 每轮都打乱顺序\n",
    "    index_array = np.arange(train_images.shape[0])\n",
    "    np.random.shuffle(index_array)\n",
    "    total_errors = 0\n",
    "    for idx in index_array:\n",
    "        x = train_images[idx]\n",
    "        y_true = train_labels[idx]\n",
    "        scores = np.dot(weights, x) + bias\n",
    "        y_pred = np.argmax(scores)\n",
    "        if y_pred != y_true:\n",
    "            weights[y_true] = weights[y_true] + x\n",
    "            bias[y_true] = bias[y_true] + 1.0\n",
    "            weights[y_pred] = weights[y_pred] - x\n",
    "            bias[y_pred] = bias[y_pred] - 1.0\n",
    "            total_errors = total_errors + 1\n",
    "    train_scores = np.dot(train_images, weights.T) + bias\n",
    "    train_predictions = np.argmax(train_scores, axis=1)\n",
    "    train_accuracy = (train_predictions == train_labels).mean()\n",
    "    print(\"第\", epoch + 1, \"轮训练结束，训练集准确率:\", float(train_accuracy), \"错误样本数:\", int(total_errors))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "3c181ad8",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-11-03T08:15:11.332653Z",
     "iopub.status.busy": "2025-11-03T08:15:11.332578Z",
     "iopub.status.idle": "2025-11-03T08:15:11.344719Z",
     "shell.execute_reply": "2025-11-03T08:15:11.344517Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "最终训练集准确率: 0.8956833333333334\n",
      "最终测试集准确率: 0.8931\n"
     ]
    }
   ],
   "source": [
    "# 在训练集和测试集上评估\n",
    "train_scores = np.dot(train_images, weights.T) + bias\n",
    "train_predictions = np.argmax(train_scores, axis=1)\n",
    "train_accuracy = (train_predictions == train_labels).mean()\n",
    "\n",
    "test_scores = np.dot(test_images, weights.T) + bias\n",
    "test_predictions = np.argmax(test_scores, axis=1)\n",
    "test_accuracy = (test_predictions == test_labels).mean()\n",
    "\n",
    "print(\"最终训练集准确率:\", float(train_accuracy))\n",
    "print(\"最终测试集准确率:\", float(test_accuracy))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "a510e522",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-11-03T08:15:11.345609Z",
     "iopub.status.busy": "2025-11-03T08:15:11.345553Z",
     "iopub.status.idle": "2025-11-03T08:15:11.349275Z",
     "shell.execute_reply": "2025-11-03T08:15:11.349111Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "测试集混淆矩阵:\n",
      "[[ 937    0    4    2    0   24    9    4    0    0]\n",
      " [   0 1123    2    2    0    1    4    1    2    0]\n",
      " [   5   43  888   39    8    9    9   12   15    4]\n",
      " [   2    3   14  942    0   26    3    8    8    4]\n",
      " [   1    6    8    5  872    0   13   25   10   42]\n",
      " [  10    5    3   48    5  780    9    7   20    5]\n",
      " [  12    5   15    2    4   39  877    3    1    0]\n",
      " [   1   12   13   18    4    2    0  960    1   17]\n",
      " [   8   56    7  102   10   56   13   12  681   29]\n",
      " [   4   11    1   23   35   13    0   47    4  871]]\n"
     ]
    }
   ],
   "source": [
    "# 计算测试集的混淆矩阵\n",
    "confusion_matrix = np.zeros((num_classes, num_classes), dtype=int)\n",
    "for real, pred in zip(test_labels, test_predictions):\n",
    "    confusion_matrix[real, pred] = confusion_matrix[real, pred] + 1\n",
    "\n",
    "print(\"测试集混淆矩阵:\")\n",
    "print(confusion_matrix)"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
